/*------------------------------------------------------------------------------
 * Copyright 2012 NCR Corporation
 *------------------------------------------------------------------------------
 * All revision information is updated automatically from source code control
 * change records - please do not manually edit.
 *------------------------------------------------------------------------------
 *
 * $LastChangedRevision:: 1742                                     $
 * $LastChangedBy:: rb250096                                       $
 * $LastChangedDate:: 2012-03-09 03:38:18 -0500 (Fri, 09 Mar 2012) $
 */

package com.ncr.nep.dashboardcore.rest.client;

import java.util.HashMap;
import java.util.Map;

import com.google.gwt.core.client.GWT;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.ncr.nep.dashboardcore.utils.client.StringUtil;

/**
 * Contains methods and logic for building a simple or complex HTTP request
 * that is used in conjunction with a REST-ful API that utilizes HTTP for
 * its underlying communications protocol.
 *
 * @author rb250096
 * @version $Revision: 1742 $
 */
public class HTTPRequest {

    //--------------------------------------------------------------------------
    //
    //  Fields
    //
    //--------------------------------------------------------------------------

    /**
     * Constant defining <code>Content-Type</code> HTTP header.
     */
    public static final String CONTENT_TYPE_HEADER = "Content-Type";

    /**
     * Constant defining <code>Accept</code> HTTP header.
     */
    public static final String ACCEPT_HEADER = "Accept";
    
    /**
     * The URL of the request.
     */
    protected String url;

    /**
     * Map of header names to values.
     */
    protected Map<String, String> headers = new HashMap<String, String>();

    /**
     * Map of form parameters names to values.
     */
    protected Map<String, String> formParams;

    /**
     * Map of URL path parameters to values.
     */
    protected Map<String, String> pathParams;

    /**
     * Data object sent in body of request.
     */
    protected Object data;

    /**
     * Timeout for request in milliseconds.
     */
    protected int requestTimeout;

    //--------------------------------------------------------------------------
    //
    //  Constructors
    //
    //--------------------------------------------------------------------------

    /**
     * Constructor
     */
    public HTTPRequest() { }

    /**
     * Constructor
     *
     * @param url The URL of the request.  If using placeholders, the string
     *            should specify each placholder with the token
     *            <code>{key}</code>, where <code>key</code> is the key portion
     *            of a key/value pair added to the request via the
     *            {@link #pathParam(String, Object)} method.
     */
    public HTTPRequest(String url) {
        this.url = url;
    }

    //--------------------------------------------------------------------------
    //
    //  Methods
    //
    //--------------------------------------------------------------------------

    /**
     * Sets the URL for the request.  This can either be just a basic URL or it
     * can be a format specifying path parameter placeholders that can be
     * substituted.
     *
     * @param url The URL of the request.  If using placeholders, the string
     *            should specify each placholder with the token
     *            <code>{key}</code>, where <code>key</code> is the key portion
     *            of a key/value pair added to the request via the
     *            {@link #pathParam(String, Object)} method.
     *
     * @return this
     */
    public HTTPRequest url(String url) {
        this.url = url;
        return this;    
    }

    /**
     * Sets the <code>Content-Type</code> header for the request.
     *
     * @param mimeType The MIME type.
     *
     * @return this
     */
    public HTTPRequest contentType(MIMEType mimeType) {
        return this.header(CONTENT_TYPE_HEADER, mimeType.toString());
    }

    /**
     * Sets the <code>Content-Type</code> header for the request.
     *
     * @param mimeType The MIME type.
     *
     * @return this
     */
    public HTTPRequest contentType(String mimeType) {
        return this.header(CONTENT_TYPE_HEADER, mimeType);
    }

    /**
     * Sets the <code>Accept</code> header for the request.
     *
     * @param mimeType The MIME type to accept in the response.
     *
     * @return this
     */
    public HTTPRequest accept(String mimeType) {
        return this.header(ACCEPT_HEADER, mimeType);
    }

    /**
     * Sets the <code>Accept</code> header for the request.
     *
     * @param mimeType The MIME type to accept in the response.
     *
     * @return this
     */
    public HTTPRequest accept(MIMEType mimeType) {
        return this.header(ACCEPT_HEADER, mimeType.toString());
    }

    /**
     * Sets the specified header for the request.
     * 
     * @param header The name of the header to set.
     * @param value The value to assign to the header.
     *              
     * @return this
     */
    public HTTPRequest header(String header, String value) {
        this.headers.put(header, value);
        return this;
    }

    /**
     * Sets the object representing the body of the request.  This object will
     * be converted to a string using its {@link Object#toString()} method.
     * Setting this overrides any parameters set using the
     * {@link #param(String, Object)} method.
     *
     * @param data The object representing the request body.
     *
     * @return this
     */
    public HTTPRequest data(Object data) {
        this.data = data;
        return this;
    }

    /**
     * Adds/sets a form-encoded argument for the request.  Use this method to
     * easily specify one or more key/value pairs to be sent as
     * <code>application/x-www-formencoded</code> data in a POST or as
     * query parameters in a GET/PUT.
     *
     * @param key The parameter key.
     * @param value The parameter value.
     *
     * @return this
     */
    public HTTPRequest param(String key, Object value) {
        if(this.formParams == null) {
            this.formParams = new HashMap<String, String>();
        }

        this.formParams.put(key, value.toString());
        return this;
    }

    /**
     * Adds/sets a path parameter for the request.  For a request URL that
     * specifies substitution tokens in the URL with the syntax
     * <code>{key}</code>, the token will be replaced with the specified value
     * when the request is sent to the server.
     *
     * @param key The key of the path parameter in the URL.
     * @param value The value to replace the <code>{key}</code> token with
     *              before the request is sent.
     *
     * @return this
     */
    public HTTPRequest pathParam(String key, Object value) {
        if(this.pathParams == null) {
            this.pathParams = new HashMap<String, String>();
        }

        this.pathParams.put(key, value.toString());
        return this;
    }

    /**
     * Sets the timeout in milliseconds for the request to wait for
     * completion before failing.
     *
     * @param milliseconds The number of milliseconds to wait.
     *
     * @return this
     */
    public HTTPRequest timeout(int milliseconds) {
        this.requestTimeout = milliseconds;
        return this;
    }

    /**
     * Sends the request using the HTTP <code>GET</code> method.
     *
     * @param callback Callback invoked when the request has completed
     *                 successfully or an error occurs.
     */
    public void get(RequestCallback callback) {
        this.doRequest(RequestBuilder.GET, callback);
    }

    /**
     * Sends the request using the HTTP <code>POST</code> method.
     *
     * @param callback Callback invoked when the request has completed
     *                 successfully or an error occurs.
     */
    public void post(RequestCallback callback) {
        this.doRequest(RequestBuilder.POST, callback);
    }

    /**
     * Sends the request using the HTTP <code>PUT</code> method.
     *
     * @param callback Callback invoked when the request has completed
     *                 successfully or an error occurs.
     */
    public void put(RequestCallback callback) {
        this.doRequest(RequestBuilder.PUT, callback);
    }

    /**
     * Sends the request using the HTTP <code>DELETE</code> method.
     *
     * @param callback Callback invoked when the request has completed
     *                 successfully or an error occurs.
     */
    public void delete(RequestCallback callback) {
        this.doRequest(RequestBuilder.DELETE, callback);
    }

    /**
     * Sends the request represented by this object.
     *
     * @param method The HTTP method being used.
     * @param callback The callback invoked when the request is completed.
     */
    protected void doRequest(RequestBuilder.Method method,
                             RequestCallback callback) {
        String rUrl = this.url;
        if(this.pathParams != null) {
            rUrl = StringUtil.substitute(rUrl, this.pathParams);
        }
        
        String query = null;
        if(this.formParams != null) {
            query = StringUtil.joinMap("&", "=", this.formParams);
        }
        
        if(query != null && method.equals(RequestBuilder.GET)) {
            rUrl = rUrl + "?" + query;
        }

        RequestBuilder builder = new RequestBuilder(method, rUrl);
        builder.setTimeoutMillis(this.requestTimeout);

        if(this.data != null) {
            builder.setRequestData(this.data.toString());
        }
        else if(query != null && method.equals(RequestBuilder.POST)) {
            builder.setRequestData(query);
            this.header("Content-Type",
                        MIMEType.APPLICATION_FORM_ENCODED.toString());
        }

        if(this.headers != null) {
            for(String header : this.headers.keySet()) {
                builder.setHeader(header, this.headers.get(header));                                
            }
        }
        
        builder.setCallback(callback);

        assert GWT.isClient() :
                "HTTPRequest can only be sent in dev or production mode";

        try {
            builder.send();
        }
        catch(RequestException re) {
            callback.onError(null, re);
        }
    }
}
